feat(text): add MSDF font atlas text rendering module - #591
Conversation
Adds `@forge-game-engine/forge/text`: `loadFontAtlas`/`FontAtlas` (`/asset-loading`) parse an msdf-atlas-gen JSON metrics file and atlas PNG; `createMsdfTextRenderable` (`/rendering`) builds the GPU-side renderable, reusing the sprite vertex shader/instance data layout unchanged, paired with a new MSDF fragment shader (median-of-three-channels + screen-space- derivative antialiasing); `TextEcsComponent`/`addTextComponent` and `createTextShapingEcsSystem` (`/text`) shape a string - word-wrapping, kerning, alignment, line spacing - into glyph quads held on a `TextMeshEcsComponent`, dirty-tracked so re-shaping only happens when a shape-affecting field actually changes. `createRenderEcsSystem` draws text through an isolated expansion branch that turns each glyph into a `SpriteEcsComponent`-shaped render command, so text batches and depth-sorts with sprites in the same command buffer without touching the sprite/nine-slice hot path. Closes #584. Ships the MSDF rendering path from the issue; a default shipped font, a Canvas2D prototyping escape hatch, and text effects (outline/glow/shadow) are follow-up work. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0113gCpCEDFQad8Y9dJuahSq
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
Per the add-demo skill: a new top-level /src module (text) unambiguously needs a demo. Generates a real MSDF atlas from Liberation Sans (SIL OFL 1.1, bundled) via the actual Chlumsky msdf-atlas-gen toolchain (built from source and vendored as static assets, see the README alongside them), rather than skipping the demo for lack of a font asset. The demo shows word-wrap/kerning/alignment side by side (left/center/ right, same paragraph) plus a live per-frame-updated counter label to demonstrate createTextShapingEcsSystem's dirty tracking in action. Also updates the text module's docs to link to the new demo. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0113gCpCEDFQad8Y9dJuahSq
|
Added an interactive Text demo per the Rather than skip it for lack of a font asset, built the actual The demo shows word-wrap/kerning/alignment side by side (left/center/right, same paragraph) plus a live per-frame-updated counter label demonstrating Generated by Claude Code |
… Sans Liberation Sans is SIL OFL 1.1, which raised concerns about how the modification/redistribution terms read even though OFL is designed to be embed-friendly. Open Sans (Apache License 2.0, full text bundled) has no font-specific license terms at all - just the same permissive terms as any other Apache-2.0 software - so it sidesteps the question entirely. Regenerated the MSDF atlas from the real msdf-atlas-gen toolchain against OpenSans-Regular.ttf and re-verified end-to-end (build, typecheck, and a real browser load confirming crisp rendering and the live counter still ticking). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0113gCpCEDFQad8Y9dJuahSq
The text docs were full of internal implementation detail a game author
doesn't need to know: GitHub issue references, "under the hood" render-
command internals, roadmap framing ("not yet implemented"), and a pointer
into the repo's own demo asset paths. Docs are for consumers of the
package, not people reading the engine's source.
Rewrites both pages around what a consumer actually needs: a complete,
runnable Quick Start, a full Options reference table for addTextComponent
(every field, type, default, and what it does), and a short factual list
of current constraints with no roadmap language. Trims the same kind of
internals aside out of the rendering module's overview page.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0113gCpCEDFQad8Y9dJuahSq
…ivot fix dev's #590 flipped SpriteEcsComponent.pivot to Y-up ((0, 0) bottom-left, (1, 1) top-right) and fixed computeNineSliceRegions to match, but shapeText has its own separate pivot math that was explicitly modeled on nine-slice's old (Y-down) formula. That's now inconsistent with the rest of the engine, including the sprite instancing pipeline text's own glyph quads render through. Applies the same fix nine-slice got: pivot.y's contribution flips via (1 - pivot.y) instead of pivot.y directly. Only affects a non-centered pivot; the default (0.5, 0.5) is unaffected. Updates the pivot tests and docs to match. Also merges dev in to pick up #590 and everything else merged since this branch was created. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0113gCpCEDFQad8Y9dJuahSq
Summary
Adds a new
@forge-game-engine/forge/textmodule implementing the MSDF (multi-channel signed distance field) text rendering path proposed in #584:FontAtlastype +loadFontAtlas(/asset-loading) - parses anmsdf-atlas-genJSON metrics file and loads its atlas PNG (via the existingImageCache) into glyph advances, kerning pairs, and line metrics, all normalized to em units. Kept data-only (no WebGL) so/asset-loadingkeeps its existing zero-dependency-on-rendering direction.createMsdfTextRenderable(/rendering) - reuses the existingsprite.vertshader and its instance data layout (spriteInstanceDataSegment) completely unchanged, paired with a newmsdf.fragshader (median-of-three-channels reconstruction +fwidth-based screen-space-derivative antialiasing, the standard technique frommsdf-atlas-gen's own docs). One renderable is built perFontAtlasand shared across every entity using that font, exactly like a sprite'sRenderable.TextEcsComponent/addTextComponent,TextMeshEcsComponent, andcreateTextShapingEcsSystem(/text) -shapeText(pure, fully unit-tested) word-wraps, applies kerning, and lays out lines by alignment/line-spacing/pivot into glyph quads. The shaping system is dirty-tracked: it only re-shapes an entity whentext,font,fontSize,wrapWidth,lineSpacing,alignment, orpivotactually changed, not every frame.createRenderEcsSystemtext integration - per the issue's own risk analysis, this skips the larger "generalize nine-slice into a sharedSubQuad[]expansion" refactor in favor of the issue's suggested fallback: an isolatedpushTextRenderCommands/buildTextCameraCommandspair that turns each glyph into aSpriteEcsComponent-shaped render command. This reuses the sprite instancing/batching/depth-sort pipeline completely unchanged (zero modifications to the existing sprite/nine-slice code path) while still letting text interleave correctly with sprites in one sorted command buffer.Not included (follow-up work)
Flagged explicitly in the new docs' "Current limitations" section:
msdf-atlas-gentoolchain, which isn't available in this environment. Every project currently supplies its own atlas.fillText-to-texture fallback for quick prototyping; not built here./documentation-site/src/pages/demos/textdemo would have nothing genuine to render. Everything else in the module (shaping math, dirty tracking, shader, render-system integration) has full unit-test coverage instead.Related issue(s)
Closes #584
Verification checklist
npm run check-typespasses with 0 errorsnpm testpasses (1084 tests, 161 files)npm run lintpasses with 0 errorsnpm run cspellpasses with 0 errorsnpm run check-exportspasses (@forge-game-engine/forge/textresolves 🟢 ESM/bundler like every other subpath)index.ts,/src/index.ts, andpackage.jsonexports(new./textentry)/documentation-site/docs/docs/text(overview + an MSDF Text guide covering atlas generation, wrapping/alignment, registration order, and dirty tracking), plus updates to theasset-loadingandrenderingsection docs to cross-link itTextEcsComponent/the new msdf shader, so none needed updating - but since this PR does touch/rendering(render-system.ts, the shader cache, shader index), I still rannpm run build,documentation-site'snpm run typecheck/npm run build, and loaded the Nine-Slice demo (the closest existing exerciser of the sprite render-command path this PR adds a sibling branch next to) plus the new/docs/textand/docs/text/msdf-textpages in a real browser (Playwright/Chromium) to confirm no regressions and no MDX/build errorsChangelog
## [Unreleased]→#### AddedinCHANGELOG.mdGenerated by Claude Code